import React from 'react';
import Input from '../Input';
import FormErrorMessage from '../FormErrorMessage';
import Toggle from '../Toggle';
import Box, { BoxProps } from '@/internals/Box';
import { forwardRef } from '@/internals/utils';
import { useStyles, useCustom } from '@/internals/hooks';
import { useFormGroup } from '../FormGroup';
import { useFormControl } from '../useFormControl';
import type { CheckType } from 'schema-typed';
import type {
  ErrorMessagePlacement,
  FormControlBaseProps,
  CheckTriggerType
} from '@/internals/types';

/**
 * Props that FormControl passes to its accepter
 */
export type FormControlAccepterProps<ValueType = any> = FormControlBaseProps<ValueType>;

export interface FormControlProps<ValueType = any>
  extends BoxProps,
    Omit<React.HTMLAttributes<HTMLFormElement>, 'value' | 'onChange' | 'color'> {
  /** Proxied components */
  accepter?: React.ElementType;

  /**
   * The name of form-control, support nested path. such as `address.city`.
   * The path will be used to get and set form values.
   *
   * @example
   * ```js
   * <Form formValue={{ address: { city: 'Shanghai' } }}>
   *   <FormControl name="address.city" />
   * </Form>
   * ```
   **/
  name: string;

  /** The current value (controlled) */
  value?: ValueType;

  /** The data validation trigger type, and it wiill overrides the setting on <Form> */
  checkTrigger?: CheckTriggerType;

  /** Show error messages */
  errorMessage?: React.ReactNode;

  /** The placement of error messages */
  errorPlacement?: ErrorMessagePlacement;

  /** Make the control readonly */
  readOnly?: boolean;

  /** Render the control as plain text */
  plaintext?: boolean;

  /** Disable the form control. */
  disabled?: boolean;

  /** Asynchronous check value */
  checkAsync?: boolean;

  /** Remove field value and error message when component is unmounted  */
  shouldResetWithUnmount?: boolean;

  /** Validation rule */
  rule?: CheckType<unknown, any>;

  /** Callback fired when data changing */
  onChange?(value: ValueType, event: React.SyntheticEvent): void;
}

export interface FormControlComponent extends React.FC<FormControlProps> {
  <Accepter extends React.ElementType = typeof Input>(
    props: FormControlProps & { accepter?: Accepter } & React.ComponentPropsWithRef<Accepter>
  ): React.ReactElement | null;
}

/**
 * The `<Form.Control>` component is used to wrap the components that need to be validated.
 * @see https://rsuitejs.com/components/form/
 */
const FormControl: FormControlComponent = forwardRef<'div', FormControlProps>((props, ref) => {
  const { propsWithDefaults } = useCustom('FormControl', props);

  const {
    as,
    accepter: AccepterComponent = Input,
    classPrefix = 'form-control',
    name,
    value,
    readOnly,
    plaintext,
    disabled,
    onChange: propsOnChange,
    onBlur: propsOnBlur,
    defaultValue,
    checkTrigger,
    errorMessage,
    errorPlacement = 'bottomStart',
    checkAsync,
    shouldResetWithUnmount,
    rule,
    id,
    ...rest
  } = propsWithDefaults;

  const { controlId, helpTextId, labelId, errorMessageId } = useFormGroup(id);

  // Use the useFormControl hook to handle form control logic
  const {
    value: fieldValue,
    error: fieldError,
    plaintext: contextPlaintext,
    readOnly: contextReadOnly,
    disabled: contextDisabled,
    onChange: handleFieldChange,
    onBlur: handleFieldBlur
  } = useFormControl({
    name,
    value,
    checkTrigger,
    errorMessage,
    checkAsync,
    shouldResetWithUnmount,
    rule
  });

  // Combine props and context values
  const resolvedReadOnly = readOnly ?? contextReadOnly;
  const resolvedPlaintext = plaintext ?? contextPlaintext;
  const resolvedDisabled = disabled ?? contextDisabled;

  const { withPrefix, prefix } = useStyles(classPrefix);
  const classes = withPrefix('wrapper');

  // Handle onChange with both hook's implementation and prop callback
  const handleChange = (value: any, event: React.SyntheticEvent) => {
    handleFieldChange(value, event);
    propsOnChange?.(value, event);
  };

  // Handle onBlur with both hook's implementation and prop callback
  const handleBlur = (event: React.FocusEvent<HTMLFormElement>) => {
    handleFieldBlur(); // onBlur doesn't take parameters in the hook
    propsOnBlur?.(event);
  };

  // Toggle component is a special case that uses `checked` and `defaultChecked` instead of `value` and `defaultValue` props.
  const valueKey = (AccepterComponent as any) === Toggle ? 'checked' : 'value';
  const accepterProps = {
    // need to distinguish between undefined and null
    [valueKey]: fieldValue === undefined ? defaultValue : fieldValue
  };

  const hasError = Boolean(fieldError);

  return (
    <Box as={as} className={classes} ref={ref} data-testid="form-control-wrapper">
      <AccepterComponent
        id={controlId}
        aria-labelledby={labelId}
        aria-describedby={helpTextId}
        aria-invalid={hasError || undefined}
        aria-errormessage={hasError ? errorMessageId : undefined}
        {...accepterProps}
        {...rest}
        readOnly={resolvedReadOnly}
        plaintext={resolvedPlaintext}
        disabled={resolvedDisabled}
        name={name}
        onChange={handleChange}
        onBlur={handleBlur}
      />

      <FormErrorMessage
        id={errorMessageId}
        role="alert"
        aria-relevant="all"
        show={hasError}
        className={prefix`message-wrapper`}
        placement={errorPlacement}
      >
        {fieldError}
      </FormErrorMessage>
    </Box>
  );
}) as FormControlComponent;

FormControl.displayName = 'FormControl';

export default FormControl;
